package com.youlu.campus.wechat.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.http.MediaType;
import com.wechat.pay.java.core.http.QueryParameter;
import com.youlu.campus.base.exception.BusinessException;
import com.youlu.campus.wechat.service.WechatComplaintService;
import com.youlu.campus.wechat.entity.ComplaintNotifyResponse;
import com.youlu.campus.wechat.enums.WechatComplaintApiEnum;
import com.youlu.campus.wechat.utils.StringHashGenerator;
import com.youlu.campus.entity.WechatMerchantConfig;
import com.youlu.campus.entity.complaint.ComplaintNotify;
import com.youlu.campus.entity.complaint.MerchantComplaint;
import com.youlu.campus.entity.complaint.NotifyDecrypt;
import com.youlu.campus.entity.complaint.ReplyUserComplaint;
import com.youlu.campus.entity.complaint.vo.ComplaintHistoryResult;
import com.youlu.campus.entity.complaint.vo.ComplaintNotifyResource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;

import static com.wechat.pay.java.core.http.UrlEncoder.urlEncode;


/**
 * 商户号投诉处理
 * 注意流量探测包：WECHATPAY/SIGNTEST/
 */
@Slf4j
public class PlatformWeChatComplaintServiceImpl implements WechatComplaintService {

    /**
     * mongodb 数据库配置验证 md5  避免重复创建
     */
    private final String configMD5;

    /**
     * 私钥
     */
    private final PrivateKey merchantPrivateKey;

    /**
     * 平台证书
     */
    private final List<X509Certificate> certificate;

    /**
     * 配置
     */
    private final WechatMerchantConfig wechatMerchantConfig;

    public PlatformWeChatComplaintServiceImpl(String configMD5, WechatMerchantConfig wechatMerchantConfig, PrivateKey merchantPrivateKey, List<X509Certificate> certificate) {
        this.configMD5 = configMD5;
        this.wechatMerchantConfig = wechatMerchantConfig;
        this.merchantPrivateKey = merchantPrivateKey;
        this.certificate = certificate;
    }

    @Override
    public String getConfigMD5() {
        return configMD5;
    }

    @Override
    public NotifyDecrypt notifyParse(String signatue, String serialNo, String nonce, String timestamp, String body) throws Exception {
        log.info("notifyParse merchantId: {},merchantName: {}, httpResponse：{}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), body);
        ComplaintNotify complaintNotify = JSON.parseObject(body, ComplaintNotify.class);
        complaintNotify.setType(2);
        String buildlSignatureObject = buildSignature(complaintNotify);
        //验证签名
        checkSignature(buildlSignatureObject, serialNo, timestamp, nonce, signatue, wechatMerchantConfig, complaintNotify);
        //解密内容
        ComplaintNotifyResource resource = complaintNotify.getResource();
        AesUtil aesUtil = new AesUtil(wechatMerchantConfig.getDesc().getBytes());
        String decryptContent = aesUtil.decryptToString(resource.getAssociatedData().getBytes(),
                resource.getNonce().getBytes(),
                resource.getCiphertext());
        if (StringUtils.isBlank(decryptContent)) {
            throw new RuntimeException("解密内容失败");
        }
        log.info("解密内容->{}", decryptContent);
        NotifyDecrypt notifyDecrypt = JSON.parseObject(decryptContent, NotifyDecrypt.class);
        log.info("notifyParse result：{}", notifyDecrypt);
        return notifyDecrypt;
    }

    @Override
    public ComplaintNotifyResponse createNotifyUrl(String url) {
        log.info("createNotifyUrl merchantId: {},merchantName: {}, url：{}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), url);
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.CREATE_NOTIFY_URL;
        //设置参数
        Map<String, String> params = new HashMap<>(1);
        params.put("url", url);
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), apiEnum.getUrl(), params);
        log.info("createNotifyUrl result：{}", result);
        return JSONObject.parseObject(result, ComplaintNotifyResponse.class);
    }


    @Override
    public ComplaintNotifyResponse getNotifyUrl() {
        log.info("getNotifyUrl merchantId: {},merchantName: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName());
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.QUERY_NOTIFY_URL;
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), apiEnum.getUrl());
        ComplaintNotifyResponse complaintNotifyResponse = JSON.parseObject(result, ComplaintNotifyResponse.class);
        log.info("getNotifyUrl result：{}", result);
        return complaintNotifyResponse;
    }

    @Override
    public ComplaintNotifyResponse deleteNotifyUrl() {
        log.info("deleteNotifyUrl merchantId: {},merchantName: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName());
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.DELETE_NOTIFY_URL;
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), apiEnum.getUrl());
        ComplaintNotifyResponse complaintNotifyResponse = JSON.parseObject(result, ComplaintNotifyResponse.class);
        log.info("deleteNotifyUrl result：{}", result);
        return complaintNotifyResponse;
    }


    @Override
    public HashMap queryList(String beginDate, String endDate, Integer limit, Integer offset) {
        log.info("queryList merchantId: {},merchantName: {},beginDate: {},endDate: {},limit: {},offset: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), beginDate, endDate, limit, offset);
        //验证参数不能为null
        Stream.of(beginDate, endDate, limit, offset).forEach(Objects::requireNonNull);

        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.QUERY_COMPLAINT_LIST;
        String requestPath = apiEnum.getUrl();
        // 添加 query param
        QueryParameter queryParameter = new QueryParameter();
        queryParameter.add("begin_date", urlEncode(beginDate));
        queryParameter.add("end_date", urlEncode(endDate));
        queryParameter.add("limit", urlEncode(limit.toString()));
        queryParameter.add("offset", urlEncode(offset.toString()));
        //拼接请求参数
        requestPath += queryParameter.getQueryStr();
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), requestPath);
        log.info("queryList result: {}", result);
        HashMap map = JSON.parseObject(result, HashMap.class);
        return map;
    }

    @Override
    public MerchantComplaint queryDetail(String complaintId) {
        log.info("queryDetail merchantId: {},merchantName: {},complaintId: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), complaintId);
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.QUERY_COMPLAINT_DETAIL;
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), apiEnum.getUrl(complaintId));
        log.info("queryDetail result: {}", result);
        MerchantComplaint merchantComplaintDetail = JSON.parseObject(result, MerchantComplaint.class);
        return merchantComplaintDetail;
    }

    @Override
    public ComplaintHistoryResult negotiationHistorys(String complaintId, Integer limit, Integer offset) {
        log.info("negotiationHistorys merchantId: {},merchantName: {},complaintId: {},limit: {},offset: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), complaintId, limit, offset);
        //请求路径
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.QUERY_COMPLAINT_NEGOTIATION_HISTORY;
        String requestPath = apiEnum.getUrl(complaintId);
        // 添加 query param
        QueryParameter queryParameter = new QueryParameter();
        queryParameter.add("limit", urlEncode(limit.toString()));
        queryParameter.add("offset", urlEncode(offset.toString()));
        // 拼接路径
        requestPath += queryParameter.getQueryStr();
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), requestPath);
        log.info("negotiationHistorys result: {}", result);
        return JSON.parseObject(result, ComplaintHistoryResult.class);
    }

    @Override
    public void response(ReplyUserComplaint replyUserComplaint) {
        log.info("response merchantId: {},merchantName: {},replyUserComplaint: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), replyUserComplaint);
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.RESPONSE_COMPLAINT;
        //获取url
        String url = apiEnum.getUrl(replyUserComplaint.getComplaintId());
        //设置请求参数
        Map<String, Object> param = new HashMap<>();
        param.put("complainted_mchid", replyUserComplaint.getComplaintedMchid());
        param.put("response_content", replyUserComplaint.getResponseContent());
        param.put("response_images", replyUserComplaint.getResponseImages());
        param.put("jump_url", replyUserComplaint.getJumpUrl());
        param.put("jump_url_text", replyUserComplaint.getJumpUrlText());
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), url, param);
        log.info("response result: {}", result);
    }

    @Override
    public void complete(String complaintId) {
        log.info("complete merchantId: {},merchantName: {},complaintId: {}", wechatMerchantConfig.getMerchantId(), wechatMerchantConfig.getMerchantName(), complaintId);
        WechatComplaintApiEnum apiEnum = WechatComplaintApiEnum.COMPLETE_COMPLAINT;
        //获取url
        String url = apiEnum.getUrl(complaintId);
        //设置请求参数
        Map<String, Object> param = new HashMap<>();
        param.put("complainted_mchid", wechatMerchantConfig.getMerchantId());
        //请求微信
        String result = requestWechat(apiEnum.getMethod(), url, param);
        log.info("complete result: {}", result);
    }

    /**
     * 请求微信API接口
     *
     * @param method
     * @param url
     * @return
     */
    private String requestWechat(HttpMethod method, String url) {
        return requestWechat(method, url, null);
    }

    /**
     * 请求微信API接口
     *
     * @param method
     * @param url
     * @param body
     * @return
     */
    private String requestWechat(HttpMethod method, String url, Object body) {
        //设置请求
        HttpUriRequest request = null;
        if (HttpMethod.GET.equals(method)) {
            request = new HttpGet(url);
        }
        if (HttpMethod.PUT.equals(method)) {
            request = new HttpPut(url);
        }
        if (HttpMethod.POST.equals(method)) {
            request = new HttpPost(url);
        }
        if (HttpMethod.DELETE.equals(method)) {
            request = new HttpDelete(url);
        }
        if (request == null) {
            throw new RuntimeException("请求方式错误");
        }
        //设置请求头
        request.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        //设置请求参数
        if (Objects.nonNull(body) && request instanceof HttpEntityEnclosingRequestBase) {
            request.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
            HttpEntityEnclosingRequestBase entityRequest = (HttpEntityEnclosingRequestBase) request;
            entityRequest.setEntity(new StringEntity(JSON.toJSONString(body), "UTF-8"));
        }
        try (
                //创建连接客户端
                CloseableHttpClient wechatPayHttpClient = getWechatPayHttpClient();
                //执行
                CloseableHttpResponse response = wechatPayHttpClient.execute(request);
        ) {
            int statusCode = response.getStatusLine().getStatusCode();
            log.info("requestWechat merchantName: {}, url: {}, statusCode: {}", wechatMerchantConfig.getMerchantName(), url, statusCode);
            if (statusCode == 200 || statusCode == 204) { //处理成功
                String result = EntityUtils.toString(response.getEntity());
                log.info("requestWechat body: {}", result);
                return result;
            }
        } catch (Exception e) {
            log.error("requestWechat error merchantName: {}, url: {}, error: {}", wechatMerchantConfig.getMerchantName(), url, e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取微信连接客户端
     *
     * @return
     */
    private CloseableHttpClient getWechatPayHttpClient() {
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(this.wechatMerchantConfig.getMerchantId(), this.wechatMerchantConfig.getSerialNumber(), merchantPrivateKey)
                .withWechatpay(certificate);
        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }

    /**
     * 验证通知签名
     *
     * @param requestTimestamp
     * @param requestNonce
     * @param requestSignature
     * @param wechatMerchantConfig
     * @param complaintNotify
     * @return
     */
    private void checkSignature(String requestBody, String serialNumber, String requestTimestamp,
                                String requestNonce,
                                String requestSignature
            , WechatMerchantConfig wechatMerchantConfig, ComplaintNotify complaintNotify) {
        try {
            log.info("【商户投诉通知】校验商户 - >{} 通知签名应答", wechatMerchantConfig.getMerchantName());

            StringBuffer sb1 = new StringBuffer();
            sb1.append(requestTimestamp).append("\n").append(requestNonce).append("\n").append(requestBody).append("\n");
            String signStr = sb1.toString();
            //获取请求体
//        String signStr =requestTimestamp+"\n"+//应答时间戳
//                requestNonce+"\n"+//应答随机串
//                requestBody+"\n";//应答请求主体
            AutoUpdateCertificatesVerifier certificatesVerifier = getCertificatesVerifier(wechatMerchantConfig);
            if (Objects.isNull(certificatesVerifier)) {
                throw new BusinessException("验证签名失败");
            }
            log.info("【商户投诉通知】商户回调通知 签名拼接字符串 ->{}", signStr);
            boolean verify = certificatesVerifier.verify(serialNumber, signStr.getBytes(), requestSignature);
            if (!verify) {
                log.info("【商户投诉通知】签名验证不通过->{}", wechatMerchantConfig.getMerchantName());
                /*ComplaintSignatureLog complaintSignatureLog = new ComplaintSignatureLog();
                complaintSignatureLog.setMerchantId(wechatMerchantConfig.getMerchantId());
                complaintSignatureLog.setMerchantName(wechatMerchantConfig.getMerchantName());
                complaintSignatureLog.setVerifySignature(signStr);
                complaintSignatureLog.setSerialNumber(serialNumber);
                complaintSignatureLog.setNonce(requestNonce);
                complaintSignatureLog.setTimestamp(requestTimestamp);
                complaintSignatureLog.setSignature(requestSignature);
                complaintSignatureLog.setNotifyId(complaintNotify.getId());
                complaintSignatureLog.setNotifyBodyJson(requestBody);
                this.mongoTemplate.save(complaintSignatureLog);*/
                throw new BusinessException("签名验证不相等");
            }
        } catch (Exception e) {
            log.error(":>>> 验证通知签名错误:{}", e);
        }

    }

    private AutoUpdateCertificatesVerifier getCertificatesVerifier(WechatMerchantConfig wechatMerchantConfig) {
        AutoUpdateCertificatesVerifier scheduledUpdateCertificatesVerifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(wechatMerchantConfig.getMerchantId(),
                        new PrivateKeySigner(wechatMerchantConfig.getSerialNumber(), merchantPrivateKey)),
                wechatMerchantConfig.getDesc().getBytes(StandardCharsets.UTF_8));
        return scheduledUpdateCertificatesVerifier;
    }


    private String buildSignature(ComplaintNotify complaintNotify) {
        ComplaintNotifyResource resource = complaintNotify.getResource();

        StringBuilder sb = new StringBuilder();
        sb.append("{").
                append("\"id\"" + ":").append("\"" + complaintNotify.getId() + "\"").append(",").
                append("\"create_time\"" + ":").append("\"" + DateUtil.format(complaintNotify.getCreateTime(), "yyyy-MM-dd'T'HH:mm:ss+08:00") + "\"").append(",").
                append("\"resource_type\"" + ":").append("\"" + complaintNotify.getResourceType() + "\"").append(",").
                append("\"event_type\"" + ":").append("\"" + complaintNotify.getEventType() + "\"").append(",").
                append("\"summary\"" + ":").append("\"" + complaintNotify.getSummary() + "\"").append(",").
                append("\"resource\"" + ":").append("{").
                append("\"original_type\"" + ":").append("\"" + resource.getOriginalType() + "\"").append(",").
                append("\"algorithm\"" + ":").append("\"" + resource.getAlgorithm() + "\"").append(",").
                append("\"ciphertext\"" + ":").append("\"" + resource.getCiphertext() + "\"").append(",").
                append("\"associated_data\"" + ":").append("\"" + resource.getAssociatedData() + "\"").append(",").
                append("\"nonce\"" + ":").append("\"" + resource.getNonce() + "\"").
                append("}").append("}");
        return sb.toString();

    }

    public static class Builder {

        /**
         * 微信商户配置
         */
        private WechatMerchantConfig config;

        public PlatformWeChatComplaintServiceImpl.Builder config(WechatMerchantConfig config) {
            this.config = config;
            return this;
        }

        public PlatformWeChatComplaintServiceImpl build() {
            log.info("PlatformWechatComplaint Builder config:{}", config);
            String merchantId = config.getMerchantId();//商户号
            String privateKeyPath = config.getPathPrivateKey();//商户 API 私钥路径
            String merchantSerialNumber = config.getSerialNumber();//商户证书序列号
            String pathPlatformCertificate = config.getPathPlatformCertificate();//商户 平台证书地址
            Stream.of(merchantId, privateKeyPath,   merchantSerialNumber, pathPlatformCertificate).forEach(Objects::requireNonNull);
            //生成配置唯一值
            String configMD5 = StringHashGenerator.generateMD5(config.toString());
            return new PlatformWeChatComplaintServiceImpl(configMD5, config, getMerchantPrivateKey(), getCertificate());
        }

        /**
         * 获取privateKey
         *
         * @return
         */
        private PrivateKey getMerchantPrivateKey() {
            try (FileInputStream fileInputStream = new FileInputStream(config.getPathPrivateKey())) {
                PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(fileInputStream);
                return merchantPrivateKey;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new BusinessException("未找到私钥文件路径");
            } catch (IOException e) {
                e.printStackTrace();
                throw new BusinessException("文件加载异常");
            }
        }

        /**
         * 获取平台证书
         *
         * @return
         */
        private List<X509Certificate> getCertificate() {
            try (FileInputStream fileInputStream = new FileInputStream(config.getPathPlatformCertificate())) {
                List<X509Certificate> list = new ArrayList<>(2);
                list.add(PemUtil.loadCertificate(fileInputStream));
                return list;
            } catch (Exception e) {
                e.printStackTrace();
                throw new BusinessException("平台认证:文件查找不到");
            }
        }

    }

}


